use bevy_transform::components::Transform;
pub use wgpu_types::PrimitiveTopology;

use super::{
    triangle_area_normal, triangle_normal, FourIterators, Indices, MeshAttributeData,
    MeshTrianglesError, MeshVertexAttribute, MeshVertexAttributeId, MeshVertexBufferLayout,
    MeshVertexBufferLayoutRef, MeshVertexBufferLayouts, MeshWindingInvertError,
    VertexAttributeValues, VertexBufferLayout,
};
#[cfg(feature = "serialize")]
use crate::SerializedMeshAttributeData;
use alloc::collections::BTreeMap;
#[cfg(feature = "morph")]
use bevy_asset::Handle;
use bevy_asset::{Asset, RenderAssetUsages};
#[cfg(feature = "morph")]
use bevy_image::Image;
use bevy_math::{primitives::Triangle3d, *};
#[cfg(feature = "serialize")]
use bevy_platform::collections::HashMap;
use bevy_reflect::Reflect;
use bytemuck::cast_slice;
#[cfg(feature = "serialize")]
use serde::{Deserialize, Serialize};
use thiserror::Error;
use tracing::warn;
use wgpu_types::{VertexAttribute, VertexFormat, VertexStepMode};

pub const INDEX_BUFFER_ASSET_INDEX: u64 = 0;
pub const VERTEX_ATTRIBUTE_BUFFER_ID: u64 = 10;

/// A 3D object made out of vertices representing triangles, lines, or points,
/// with "attribute" values for each vertex.
///
/// Meshes can be automatically generated by a bevy `AssetLoader` (generally by loading a `Gltf` file),
/// or by converting a [primitive](bevy_math::primitives) using [`into`](Into).
/// It is also possible to create one manually. They can be edited after creation.
///
/// Meshes can be rendered with a [`Mesh2d`](crate::Mesh2d) and `MeshMaterial2d`
/// or [`Mesh3d`](crate::Mesh3d) and `MeshMaterial3d` for 2D and 3D respectively.
///
/// A [`Mesh`] in Bevy is equivalent to a "primitive" in the glTF format, for a
/// glTF Mesh representation, see `GltfMesh`.
///
/// ## Manual creation
///
/// The following function will construct a flat mesh, to be rendered with a
/// `StandardMaterial` or `ColorMaterial`:
///
/// ```
/// # use bevy_mesh::{Mesh, Indices, PrimitiveTopology};
/// # use bevy_asset::RenderAssetUsages;
/// fn create_simple_parallelogram() -> Mesh {
///     // Create a new mesh using a triangle list topology, where each set of 3 vertices composes a triangle.
///     Mesh::new(PrimitiveTopology::TriangleList, RenderAssetUsages::default())
///         // Add 4 vertices, each with its own position attribute (coordinate in
///         // 3D space), for each of the corners of the parallelogram.
///         .with_inserted_attribute(
///             Mesh::ATTRIBUTE_POSITION,
///             vec![[0.0, 0.0, 0.0], [1.0, 2.0, 0.0], [2.0, 2.0, 0.0], [1.0, 0.0, 0.0]]
///         )
///         // Assign a UV coordinate to each vertex.
///         .with_inserted_attribute(
///             Mesh::ATTRIBUTE_UV_0,
///             vec![[0.0, 1.0], [0.5, 0.0], [1.0, 0.0], [0.5, 1.0]]
///         )
///         // Assign normals (everything points outwards)
///         .with_inserted_attribute(
///             Mesh::ATTRIBUTE_NORMAL,
///             vec![[0.0, 0.0, 1.0], [0.0, 0.0, 1.0], [0.0, 0.0, 1.0], [0.0, 0.0, 1.0]]
///         )
///         // After defining all the vertices and their attributes, build each triangle using the
///         // indices of the vertices that make it up in a counter-clockwise order.
///         .with_inserted_indices(Indices::U32(vec![
///             // First triangle
///             0, 3, 1,
///             // Second triangle
///             1, 3, 2
///         ]))
/// }
/// ```
///
/// You can see how it looks like [here](https://github.com/bevyengine/bevy/blob/main/assets/docs/Mesh.png),
/// used in a [`Mesh3d`](crate::Mesh3d) with a square bevy logo texture, with added axis, points,
/// lines and text for clarity.
///
/// ## Other examples
///
/// For further visualization, explanation, and examples, see the built-in Bevy examples,
/// and the [implementation of the built-in shapes](https://github.com/bevyengine/bevy/tree/main/crates/bevy_mesh/src/primitives).
/// In particular, [generate_custom_mesh](https://github.com/bevyengine/bevy/blob/main/examples/3d/generate_custom_mesh.rs)
/// teaches you to access and modify the attributes of a [`Mesh`] after creating it.
///
/// ## Common points of confusion
///
/// - UV maps in Bevy start at the top-left, see [`ATTRIBUTE_UV_0`](Mesh::ATTRIBUTE_UV_0),
///   other APIs can have other conventions, `OpenGL` starts at bottom-left.
/// - It is possible and sometimes useful for multiple vertices to have the same
///   [position attribute](Mesh::ATTRIBUTE_POSITION) value,
///   it's a common technique in 3D modeling for complex UV mapping or other calculations.
/// - Bevy performs frustum culling based on the `Aabb` of meshes, which is calculated
///   and added automatically for new meshes only. If a mesh is modified, the entity's `Aabb`
///   needs to be updated manually or deleted so that it is re-calculated.
///
/// ## Use with `StandardMaterial`
///
/// To render correctly with `StandardMaterial`, a mesh needs to have properly defined:
/// - [`UVs`](Mesh::ATTRIBUTE_UV_0): Bevy needs to know how to map a texture onto the mesh
///   (also true for `ColorMaterial`).
/// - [`Normals`](Mesh::ATTRIBUTE_NORMAL): Bevy needs to know how light interacts with your mesh.
///   [0.0, 0.0, 1.0] is very common for simple flat meshes on the XY plane,
///   because simple meshes are smooth and they don't require complex light calculations.
/// - Vertex winding order: by default, `StandardMaterial.cull_mode` is `Some(Face::Back)`,
///   which means that Bevy would *only* render the "front" of each triangle, which
///   is the side of the triangle from where the vertices appear in a *counter-clockwise* order.
///
/// ## Remote Inspection
///
/// To transmit a [`Mesh`] between two running Bevy apps, e.g. through BRP, use [`SerializedMesh`].
/// This type is only meant for short-term transmission between same versions and should not be stored anywhere.
#[derive(Asset, Debug, Clone, Reflect, PartialEq)]
#[reflect(Clone)]
pub struct Mesh {
    #[reflect(ignore, clone)]
    primitive_topology: PrimitiveTopology,
    /// `std::collections::BTreeMap` with all defined vertex attributes (Positions, Normals, ...)
    /// for this mesh. Attribute ids to attribute values.
    /// Uses a [`BTreeMap`] because, unlike `HashMap`, it has a defined iteration order,
    /// which allows easy stable `VertexBuffers` (i.e. same buffer order)
    #[reflect(ignore, clone)]
    attributes: BTreeMap<MeshVertexAttributeId, MeshAttributeData>,
    indices: Option<Indices>,
    #[cfg(feature = "morph")]
    morph_targets: Option<Handle<Image>>,
    #[cfg(feature = "morph")]
    morph_target_names: Option<Vec<String>>,
    pub asset_usage: RenderAssetUsages,
    /// Whether or not to build a BLAS for use with `bevy_solari` raytracing.
    ///
    /// Note that this is _not_ whether the mesh is _compatible_ with `bevy_solari` raytracing.
    /// This field just controls whether or not a BLAS gets built for this mesh, assuming that
    /// the mesh is compatible.
    ///
    /// The use case for this field is using lower-resolution proxy meshes for raytracing (to save on BLAS memory usage),
    /// while using higher-resolution meshes for raster. You can set this field to true for the lower-resolution proxy mesh,
    /// and to false for the high-resolution raster mesh.
    ///
    /// Alternatively, you can use the same mesh for both raster and raytracing, with this field set to true.
    ///
    /// Does nothing if not used with `bevy_solari`, or if the mesh is not compatible
    /// with `bevy_solari` (see `bevy_solari`'s docs).
    pub enable_raytracing: bool,
}

impl Mesh {
    /// Where the vertex is located in space. Use in conjunction with [`Mesh::insert_attribute`]
    /// or [`Mesh::with_inserted_attribute`].
    ///
    /// The format of this attribute is [`VertexFormat::Float32x3`].
    pub const ATTRIBUTE_POSITION: MeshVertexAttribute =
        MeshVertexAttribute::new("Vertex_Position", 0, VertexFormat::Float32x3);

    /// The direction the vertex normal is facing in.
    /// Use in conjunction with [`Mesh::insert_attribute`] or [`Mesh::with_inserted_attribute`].
    ///
    /// The format of this attribute is [`VertexFormat::Float32x3`].
    pub const ATTRIBUTE_NORMAL: MeshVertexAttribute =
        MeshVertexAttribute::new("Vertex_Normal", 1, VertexFormat::Float32x3);

    /// Texture coordinates for the vertex. Use in conjunction with [`Mesh::insert_attribute`]
    /// or [`Mesh::with_inserted_attribute`].
    ///
    /// Generally `[0.,0.]` is mapped to the top left of the texture, and `[1.,1.]` to the bottom-right.
    ///
    /// By default values outside will be clamped per pixel not for the vertex,
    /// "stretching" the borders of the texture.
    /// This behavior can be useful in some cases, usually when the borders have only
    /// one color, for example a logo, and you want to "extend" those borders.
    ///
    /// For different mapping outside of `0..=1` range,
    /// see [`ImageAddressMode`](bevy_image::ImageAddressMode).
    ///
    /// The format of this attribute is [`VertexFormat::Float32x2`].
    pub const ATTRIBUTE_UV_0: MeshVertexAttribute =
        MeshVertexAttribute::new("Vertex_Uv", 2, VertexFormat::Float32x2);

    /// Alternate texture coordinates for the vertex. Use in conjunction with
    /// [`Mesh::insert_attribute`] or [`Mesh::with_inserted_attribute`].
    ///
    /// Typically, these are used for lightmaps, textures that provide
    /// precomputed illumination.
    ///
    /// The format of this attribute is [`VertexFormat::Float32x2`].
    pub const ATTRIBUTE_UV_1: MeshVertexAttribute =
        MeshVertexAttribute::new("Vertex_Uv_1", 3, VertexFormat::Float32x2);

    /// The direction of the vertex tangent. Used for normal mapping.
    /// Usually generated with [`generate_tangents`](Mesh::generate_tangents) or
    /// [`with_generated_tangents`](Mesh::with_generated_tangents).
    ///
    /// The format of this attribute is [`VertexFormat::Float32x4`].
    pub const ATTRIBUTE_TANGENT: MeshVertexAttribute =
        MeshVertexAttribute::new("Vertex_Tangent", 4, VertexFormat::Float32x4);

    /// Per vertex coloring. Use in conjunction with [`Mesh::insert_attribute`]
    /// or [`Mesh::with_inserted_attribute`].
    ///
    /// The format of this attribute is [`VertexFormat::Float32x4`].
    pub const ATTRIBUTE_COLOR: MeshVertexAttribute =
        MeshVertexAttribute::new("Vertex_Color", 5, VertexFormat::Float32x4);

    /// Per vertex joint transform matrix weight. Use in conjunction with [`Mesh::insert_attribute`]
    /// or [`Mesh::with_inserted_attribute`].
    ///
    /// The format of this attribute is [`VertexFormat::Float32x4`].
    pub const ATTRIBUTE_JOINT_WEIGHT: MeshVertexAttribute =
        MeshVertexAttribute::new("Vertex_JointWeight", 6, VertexFormat::Float32x4);

    /// Per vertex joint transform matrix index. Use in conjunction with [`Mesh::insert_attribute`]
    /// or [`Mesh::with_inserted_attribute`].
    ///
    /// The format of this attribute is [`VertexFormat::Uint16x4`].
    pub const ATTRIBUTE_JOINT_INDEX: MeshVertexAttribute =
        MeshVertexAttribute::new("Vertex_JointIndex", 7, VertexFormat::Uint16x4);

    /// The first index that can be used for custom vertex attributes.
    /// Only the attributes with an index below this are used by Bevy.
    pub const FIRST_AVAILABLE_CUSTOM_ATTRIBUTE: u64 = 8;

    /// Construct a new mesh. You need to provide a [`PrimitiveTopology`] so that the
    /// renderer knows how to treat the vertex data. Most of the time this will be
    /// [`PrimitiveTopology::TriangleList`].
    pub fn new(primitive_topology: PrimitiveTopology, asset_usage: RenderAssetUsages) -> Self {
        Mesh {
            primitive_topology,
            attributes: Default::default(),
            indices: None,
            #[cfg(feature = "morph")]
            morph_targets: None,
            #[cfg(feature = "morph")]
            morph_target_names: None,
            asset_usage,
            enable_raytracing: true,
        }
    }

    /// Returns the topology of the mesh.
    pub fn primitive_topology(&self) -> PrimitiveTopology {
        self.primitive_topology
    }

    /// Sets the data for a vertex attribute (position, normal, etc.). The name will
    /// often be one of the associated constants such as [`Mesh::ATTRIBUTE_POSITION`].
    ///
    /// `Aabb` of entities with modified mesh are not updated automatically.
    ///
    /// # Panics
    /// Panics when the format of the values does not match the attribute's format.
    #[inline]
    pub fn insert_attribute(
        &mut self,
        attribute: MeshVertexAttribute,
        values: impl Into<VertexAttributeValues>,
    ) {
        let values = values.into();
        let values_format = VertexFormat::from(&values);
        if values_format != attribute.format {
            panic!(
                "Failed to insert attribute. Invalid attribute format for {}. Given format is {values_format:?} but expected {:?}",
                attribute.name, attribute.format
            );
        }

        self.attributes
            .insert(attribute.id, MeshAttributeData { attribute, values });
    }

    /// Consumes the mesh and returns a mesh with data set for a vertex attribute (position, normal, etc.).
    /// The name will often be one of the associated constants such as [`Mesh::ATTRIBUTE_POSITION`].
    ///
    /// (Alternatively, you can use [`Mesh::insert_attribute`] to mutate an existing mesh in-place)
    ///
    /// `Aabb` of entities with modified mesh are not updated automatically.
    ///
    /// # Panics
    /// Panics when the format of the values does not match the attribute's format.
    #[must_use]
    #[inline]
    pub fn with_inserted_attribute(
        mut self,
        attribute: MeshVertexAttribute,
        values: impl Into<VertexAttributeValues>,
    ) -> Self {
        self.insert_attribute(attribute, values);
        self
    }

    /// Removes the data for a vertex attribute
    pub fn remove_attribute(
        &mut self,
        attribute: impl Into<MeshVertexAttributeId>,
    ) -> Option<VertexAttributeValues> {
        self.attributes
            .remove(&attribute.into())
            .map(|data| data.values)
    }

    /// Consumes the mesh and returns a mesh without the data for a vertex attribute
    ///
    /// (Alternatively, you can use [`Mesh::remove_attribute`] to mutate an existing mesh in-place)
    #[must_use]
    pub fn with_removed_attribute(mut self, attribute: impl Into<MeshVertexAttributeId>) -> Self {
        self.remove_attribute(attribute);
        self
    }

    #[inline]
    pub fn contains_attribute(&self, id: impl Into<MeshVertexAttributeId>) -> bool {
        self.attributes.contains_key(&id.into())
    }

    /// Retrieves the data currently set to the vertex attribute with the specified [`MeshVertexAttributeId`].
    #[inline]
    pub fn attribute(
        &self,
        id: impl Into<MeshVertexAttributeId>,
    ) -> Option<&VertexAttributeValues> {
        self.attributes.get(&id.into()).map(|data| &data.values)
    }

    /// Retrieves the full data currently set to the vertex attribute with the specified [`MeshVertexAttributeId`].
    #[inline]
    pub(crate) fn attribute_data(
        &self,
        id: impl Into<MeshVertexAttributeId>,
    ) -> Option<&MeshAttributeData> {
        self.attributes.get(&id.into())
    }

    /// Retrieves the data currently set to the vertex attribute with the specified `name` mutably.
    #[inline]
    pub fn attribute_mut(
        &mut self,
        id: impl Into<MeshVertexAttributeId>,
    ) -> Option<&mut VertexAttributeValues> {
        self.attributes
            .get_mut(&id.into())
            .map(|data| &mut data.values)
    }

    /// Returns an iterator that yields references to the data of each vertex attribute.
    pub fn attributes(
        &self,
    ) -> impl Iterator<Item = (&MeshVertexAttribute, &VertexAttributeValues)> {
        self.attributes
            .values()
            .map(|data| (&data.attribute, &data.values))
    }

    /// Returns an iterator that yields mutable references to the data of each vertex attribute.
    pub fn attributes_mut(
        &mut self,
    ) -> impl Iterator<Item = (&MeshVertexAttribute, &mut VertexAttributeValues)> {
        self.attributes
            .values_mut()
            .map(|data| (&data.attribute, &mut data.values))
    }

    /// Sets the vertex indices of the mesh. They describe how triangles are constructed out of the
    /// vertex attributes and are therefore only useful for the [`PrimitiveTopology`] variants
    /// that use triangles.
    #[inline]
    pub fn insert_indices(&mut self, indices: Indices) {
        self.indices = Some(indices);
    }

    /// Consumes the mesh and returns a mesh with the given vertex indices. They describe how triangles
    /// are constructed out of the vertex attributes and are therefore only useful for the
    /// [`PrimitiveTopology`] variants that use triangles.
    ///
    /// (Alternatively, you can use [`Mesh::insert_indices`] to mutate an existing mesh in-place)
    #[must_use]
    #[inline]
    pub fn with_inserted_indices(mut self, indices: Indices) -> Self {
        self.insert_indices(indices);
        self
    }

    /// Retrieves the vertex `indices` of the mesh.
    #[inline]
    pub fn indices(&self) -> Option<&Indices> {
        self.indices.as_ref()
    }

    /// Retrieves the vertex `indices` of the mesh mutably.
    #[inline]
    pub fn indices_mut(&mut self) -> Option<&mut Indices> {
        self.indices.as_mut()
    }

    /// Removes the vertex `indices` from the mesh and returns them.
    #[inline]
    pub fn remove_indices(&mut self) -> Option<Indices> {
        core::mem::take(&mut self.indices)
    }

    /// Consumes the mesh and returns a mesh without the vertex `indices` of the mesh.
    ///
    /// (Alternatively, you can use [`Mesh::remove_indices`] to mutate an existing mesh in-place)
    #[must_use]
    pub fn with_removed_indices(mut self) -> Self {
        self.remove_indices();
        self
    }

    /// Returns the size of a vertex in bytes.
    pub fn get_vertex_size(&self) -> u64 {
        self.attributes
            .values()
            .map(|data| data.attribute.format.size())
            .sum()
    }

    /// Returns the size required for the vertex buffer in bytes.
    pub fn get_vertex_buffer_size(&self) -> usize {
        let vertex_size = self.get_vertex_size() as usize;
        let vertex_count = self.count_vertices();
        vertex_count * vertex_size
    }

    /// Computes and returns the index data of the mesh as bytes.
    /// This is used to transform the index data into a GPU friendly format.
    pub fn get_index_buffer_bytes(&self) -> Option<&[u8]> {
        self.indices.as_ref().map(|indices| match &indices {
            Indices::U16(indices) => cast_slice(&indices[..]),
            Indices::U32(indices) => cast_slice(&indices[..]),
        })
    }

    /// Get this `Mesh`'s [`MeshVertexBufferLayout`], used in `SpecializedMeshPipeline`.
    pub fn get_mesh_vertex_buffer_layout(
        &self,
        mesh_vertex_buffer_layouts: &mut MeshVertexBufferLayouts,
    ) -> MeshVertexBufferLayoutRef {
        let mut attributes = Vec::with_capacity(self.attributes.len());
        let mut attribute_ids = Vec::with_capacity(self.attributes.len());
        let mut accumulated_offset = 0;
        for (index, data) in self.attributes.values().enumerate() {
            attribute_ids.push(data.attribute.id);
            attributes.push(VertexAttribute {
                offset: accumulated_offset,
                format: data.attribute.format,
                shader_location: index as u32,
            });
            accumulated_offset += data.attribute.format.size();
        }

        let layout = MeshVertexBufferLayout {
            layout: VertexBufferLayout {
                array_stride: accumulated_offset,
                step_mode: VertexStepMode::Vertex,
                attributes,
            },
            attribute_ids,
        };
        mesh_vertex_buffer_layouts.insert(layout)
    }

    /// Counts all vertices of the mesh.
    ///
    /// If the attributes have different vertex counts, the smallest is returned.
    pub fn count_vertices(&self) -> usize {
        let mut vertex_count: Option<usize> = None;
        for (attribute_id, attribute_data) in &self.attributes {
            let attribute_len = attribute_data.values.len();
            if let Some(previous_vertex_count) = vertex_count {
                if previous_vertex_count != attribute_len {
                    let name = self
                        .attributes
                        .get(attribute_id)
                        .map(|data| data.attribute.name.to_string())
                        .unwrap_or_else(|| format!("{attribute_id:?}"));

                    warn!("{name} has a different vertex count ({attribute_len}) than other attributes ({previous_vertex_count}) in this mesh, \
                        all attributes will be truncated to match the smallest.");
                    vertex_count = Some(core::cmp::min(previous_vertex_count, attribute_len));
                }
            } else {
                vertex_count = Some(attribute_len);
            }
        }

        vertex_count.unwrap_or(0)
    }

    /// Computes and returns the vertex data of the mesh as bytes.
    /// Therefore the attributes are located in the order of their [`MeshVertexAttribute::id`].
    /// This is used to transform the vertex data into a GPU friendly format.
    ///
    /// If the vertex attributes have different lengths, they are all truncated to
    /// the length of the smallest.
    ///
    /// This is a convenience method which allocates a Vec.
    /// Prefer pre-allocating and using [`Mesh::write_packed_vertex_buffer_data`] when possible.
    pub fn create_packed_vertex_buffer_data(&self) -> Vec<u8> {
        let mut attributes_interleaved_buffer = vec![0; self.get_vertex_buffer_size()];
        self.write_packed_vertex_buffer_data(&mut attributes_interleaved_buffer);
        attributes_interleaved_buffer
    }

    /// Computes and write the vertex data of the mesh into a mutable byte slice.
    /// The attributes are located in the order of their [`MeshVertexAttribute::id`].
    /// This is used to transform the vertex data into a GPU friendly format.
    ///
    /// If the vertex attributes have different lengths, they are all truncated to
    /// the length of the smallest.
    pub fn write_packed_vertex_buffer_data(&self, slice: &mut [u8]) {
        let vertex_size = self.get_vertex_size() as usize;
        let vertex_count = self.count_vertices();
        // bundle into interleaved buffers
        let mut attribute_offset = 0;
        for attribute_data in self.attributes.values() {
            let attribute_size = attribute_data.attribute.format.size() as usize;
            let attributes_bytes = attribute_data.values.get_bytes();
            for (vertex_index, attribute_bytes) in attributes_bytes
                .chunks_exact(attribute_size)
                .take(vertex_count)
                .enumerate()
            {
                let offset = vertex_index * vertex_size + attribute_offset;
                slice[offset..offset + attribute_size].copy_from_slice(attribute_bytes);
            }

            attribute_offset += attribute_size;
        }
    }

    /// Duplicates the vertex attributes so that no vertices are shared.
    ///
    /// This can dramatically increase the vertex count, so make sure this is what you want.
    /// Does nothing if no [Indices] are set.
    pub fn duplicate_vertices(&mut self) {
        fn duplicate<T: Copy>(values: &[T], indices: impl Iterator<Item = usize>) -> Vec<T> {
            indices.map(|i| values[i]).collect()
        }

        let Some(indices) = self.indices.take() else {
            return;
        };

        for attributes in self.attributes.values_mut() {
            let indices = indices.iter();
            #[expect(
                clippy::match_same_arms,
                reason = "Although the `vec` binding on some match arms may have different types, each variant has different semantics; thus it's not guaranteed that they will use the same type forever."
            )]
            match &mut attributes.values {
                VertexAttributeValues::Float32(vec) => *vec = duplicate(vec, indices),
                VertexAttributeValues::Sint32(vec) => *vec = duplicate(vec, indices),
                VertexAttributeValues::Uint32(vec) => *vec = duplicate(vec, indices),
                VertexAttributeValues::Float32x2(vec) => *vec = duplicate(vec, indices),
                VertexAttributeValues::Sint32x2(vec) => *vec = duplicate(vec, indices),
                VertexAttributeValues::Uint32x2(vec) => *vec = duplicate(vec, indices),
                VertexAttributeValues::Float32x3(vec) => *vec = duplicate(vec, indices),
                VertexAttributeValues::Sint32x3(vec) => *vec = duplicate(vec, indices),
                VertexAttributeValues::Uint32x3(vec) => *vec = duplicate(vec, indices),
                VertexAttributeValues::Sint32x4(vec) => *vec = duplicate(vec, indices),
                VertexAttributeValues::Uint32x4(vec) => *vec = duplicate(vec, indices),
                VertexAttributeValues::Float32x4(vec) => *vec = duplicate(vec, indices),
                VertexAttributeValues::Sint16x2(vec) => *vec = duplicate(vec, indices),
                VertexAttributeValues::Snorm16x2(vec) => *vec = duplicate(vec, indices),
                VertexAttributeValues::Uint16x2(vec) => *vec = duplicate(vec, indices),
                VertexAttributeValues::Unorm16x2(vec) => *vec = duplicate(vec, indices),
                VertexAttributeValues::Sint16x4(vec) => *vec = duplicate(vec, indices),
                VertexAttributeValues::Snorm16x4(vec) => *vec = duplicate(vec, indices),
                VertexAttributeValues::Uint16x4(vec) => *vec = duplicate(vec, indices),
                VertexAttributeValues::Unorm16x4(vec) => *vec = duplicate(vec, indices),
                VertexAttributeValues::Sint8x2(vec) => *vec = duplicate(vec, indices),
                VertexAttributeValues::Snorm8x2(vec) => *vec = duplicate(vec, indices),
                VertexAttributeValues::Uint8x2(vec) => *vec = duplicate(vec, indices),
                VertexAttributeValues::Unorm8x2(vec) => *vec = duplicate(vec, indices),
                VertexAttributeValues::Sint8x4(vec) => *vec = duplicate(vec, indices),
                VertexAttributeValues::Snorm8x4(vec) => *vec = duplicate(vec, indices),
                VertexAttributeValues::Uint8x4(vec) => *vec = duplicate(vec, indices),
                VertexAttributeValues::Unorm8x4(vec) => *vec = duplicate(vec, indices),
            }
        }
    }

    /// Consumes the mesh and returns a mesh with no shared vertices.
    ///
    /// This can dramatically increase the vertex count, so make sure this is what you want.
    /// Does nothing if no [`Indices`] are set.
    ///
    /// (Alternatively, you can use [`Mesh::duplicate_vertices`] to mutate an existing mesh in-place)
    #[must_use]
    pub fn with_duplicated_vertices(mut self) -> Self {
        self.duplicate_vertices();
        self
    }

    /// Inverts the winding of the indices such that all counter-clockwise triangles are now
    /// clockwise and vice versa.
    /// For lines, their start and end indices are flipped.
    ///
    /// Does nothing if no [`Indices`] are set.
    /// If this operation succeeded, an [`Ok`] result is returned.
    pub fn invert_winding(&mut self) -> Result<(), MeshWindingInvertError> {
        fn invert<I>(
            indices: &mut [I],
            topology: PrimitiveTopology,
        ) -> Result<(), MeshWindingInvertError> {
            match topology {
                PrimitiveTopology::TriangleList => {
                    // Early return if the index count doesn't match
                    if !indices.len().is_multiple_of(3) {
                        return Err(MeshWindingInvertError::AbruptIndicesEnd);
                    }
                    for chunk in indices.chunks_mut(3) {
                        // This currently can only be optimized away with unsafe, rework this when `feature(slice_as_chunks)` gets stable.
                        let [_, b, c] = chunk else {
                            return Err(MeshWindingInvertError::AbruptIndicesEnd);
                        };
                        core::mem::swap(b, c);
                    }
                    Ok(())
                }
                PrimitiveTopology::LineList => {
                    // Early return if the index count doesn't match
                    if !indices.len().is_multiple_of(2) {
                        return Err(MeshWindingInvertError::AbruptIndicesEnd);
                    }
                    indices.reverse();
                    Ok(())
                }
                PrimitiveTopology::TriangleStrip | PrimitiveTopology::LineStrip => {
                    indices.reverse();
                    Ok(())
                }
                _ => Err(MeshWindingInvertError::WrongTopology),
            }
        }
        match &mut self.indices {
            Some(Indices::U16(vec)) => invert(vec, self.primitive_topology),
            Some(Indices::U32(vec)) => invert(vec, self.primitive_topology),
            None => Ok(()),
        }
    }

    /// Consumes the mesh and returns a mesh with inverted winding of the indices such
    /// that all counter-clockwise triangles are now clockwise and vice versa.
    ///
    /// Does nothing if no [`Indices`] are set.
    pub fn with_inverted_winding(mut self) -> Result<Self, MeshWindingInvertError> {
        self.invert_winding().map(|_| self)
    }

    /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of a mesh.
    /// If the mesh is indexed, this defaults to smooth normals. Otherwise, it defaults to flat
    /// normals.
    ///
    /// # Panics
    /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`.
    /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`].=
    pub fn compute_normals(&mut self) {
        assert!(
            matches!(self.primitive_topology, PrimitiveTopology::TriangleList),
            "`compute_normals` can only work on `TriangleList`s"
        );
        if self.indices().is_none() {
            self.compute_flat_normals();
        } else {
            self.compute_smooth_normals();
        }
    }

    /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of a mesh.
    ///
    /// # Panics
    /// Panics if [`Indices`] are set or [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`.
    /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`].
    /// Consider calling [`Mesh::duplicate_vertices`] or exporting your mesh with normal
    /// attributes.
    ///
    /// FIXME: This should handle more cases since this is called as a part of gltf
    /// mesh loading where we can't really blame users for loading meshes that might
    /// not conform to the limitations here!
    pub fn compute_flat_normals(&mut self) {
        assert!(
            self.indices().is_none(),
            "`compute_flat_normals` can't work on indexed geometry. Consider calling either `Mesh::compute_smooth_normals` or `Mesh::duplicate_vertices` followed by `Mesh::compute_flat_normals`."
        );
        assert!(
            matches!(self.primitive_topology, PrimitiveTopology::TriangleList),
            "`compute_flat_normals` can only work on `TriangleList`s"
        );

        let positions = self
            .attribute(Mesh::ATTRIBUTE_POSITION)
            .unwrap()
            .as_float3()
            .expect("`Mesh::ATTRIBUTE_POSITION` vertex attributes should be of type `float3`");

        let normals: Vec<_> = positions
            .chunks_exact(3)
            .map(|p| triangle_normal(p[0], p[1], p[2]))
            .flat_map(|normal| [normal; 3])
            .collect();

        self.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
    }

    /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of an indexed mesh, smoothing normals for shared
    /// vertices.
    ///
    /// This method weights normals by the angles of the corners of connected triangles, thus
    /// eliminating triangle area and count as factors in the final normal. This does make it
    /// somewhat slower than [`Mesh::compute_area_weighted_normals`] which does not need to
    /// greedily normalize each triangle's normal or calculate corner angles.
    ///
    /// If you would rather have the computed normals be weighted by triangle area, see
    /// [`Mesh::compute_area_weighted_normals`] instead. If you need to weight them in some other
    /// way, see [`Mesh::compute_custom_smooth_normals`].
    ///
    /// # Panics
    /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`.
    /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`].
    /// Panics if the mesh does not have indices defined.
    pub fn compute_smooth_normals(&mut self) {
        self.compute_custom_smooth_normals(|[a, b, c], positions, normals| {
            let pa = Vec3::from(positions[a]);
            let pb = Vec3::from(positions[b]);
            let pc = Vec3::from(positions[c]);

            let ab = pb - pa;
            let ba = pa - pb;
            let bc = pc - pb;
            let cb = pb - pc;
            let ca = pa - pc;
            let ac = pc - pa;

            const EPS: f32 = f32::EPSILON;
            let weight_a = if ab.length_squared() * ac.length_squared() > EPS {
                ab.angle_between(ac)
            } else {
                0.0
            };
            let weight_b = if ba.length_squared() * bc.length_squared() > EPS {
                ba.angle_between(bc)
            } else {
                0.0
            };
            let weight_c = if ca.length_squared() * cb.length_squared() > EPS {
                ca.angle_between(cb)
            } else {
                0.0
            };

            let normal = Vec3::from(triangle_normal(positions[a], positions[b], positions[c]));

            normals[a] += normal * weight_a;
            normals[b] += normal * weight_b;
            normals[c] += normal * weight_c;
        });
    }

    /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of an indexed mesh, smoothing normals for shared
    /// vertices.
    ///
    /// This method weights normals by the area of each triangle containing the vertex. Thus,
    /// larger triangles will skew the normals of their vertices towards their own normal more
    /// than smaller triangles will.
    ///
    /// This method is actually somewhat faster than [`Mesh::compute_smooth_normals`] because an
    /// intermediate result of triangle normal calculation is already scaled by the triangle's area.
    ///
    /// If you would rather have the computed normals be influenced only by the angles of connected
    /// edges, see [`Mesh::compute_smooth_normals`] instead. If you need to weight them in some
    /// other way, see [`Mesh::compute_custom_smooth_normals`].
    ///
    /// # Panics
    /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`.
    /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`].
    /// Panics if the mesh does not have indices defined.
    pub fn compute_area_weighted_normals(&mut self) {
        self.compute_custom_smooth_normals(|[a, b, c], positions, normals| {
            let normal = Vec3::from(triangle_area_normal(
                positions[a],
                positions[b],
                positions[c],
            ));
            [a, b, c].into_iter().for_each(|pos| {
                normals[pos] += normal;
            });
        });
    }

    /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of an indexed mesh, smoothing normals for shared
    /// vertices.
    ///
    /// This method allows you to customize how normals are weighted via the `per_triangle` parameter,
    /// which must be a function or closure that accepts 3 parameters:
    /// - The indices of the three vertices of the triangle as a `[usize; 3]`.
    /// - A reference to the values of the [`Mesh::ATTRIBUTE_POSITION`] of the mesh (`&[[f32; 3]]`).
    /// - A mutable reference to the sums of all normals so far.
    ///
    /// See also the standard methods included in Bevy for calculating smooth normals:
    /// - [`Mesh::compute_smooth_normals`]
    /// - [`Mesh::compute_area_weighted_normals`]
    ///
    /// An example that would weight each connected triangle's normal equally, thus skewing normals
    /// towards the planes divided into the most triangles:
    /// ```
    /// # use bevy_asset::RenderAssetUsages;
    /// # use bevy_mesh::{Mesh, PrimitiveTopology, Meshable, MeshBuilder};
    /// # use bevy_math::{Vec3, primitives::Cuboid};
    /// # let mut mesh = Cuboid::default().mesh().build();
    /// mesh.compute_custom_smooth_normals(|[a, b, c], positions, normals| {
    ///     let normal = Vec3::from(bevy_mesh::triangle_normal(positions[a], positions[b], positions[c]));
    ///     for idx in [a, b, c] {
    ///         normals[idx] += normal;
    ///     }
    /// });
    /// ```
    ///
    /// # Panics
    /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`.
    /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`].
    /// Panics if the mesh does not have indices defined.
    //
    // FIXME: This should handle more cases since this is called as a part of gltf
    // mesh loading where we can't really blame users for loading meshes that might
    // not conform to the limitations here!
    //
    // When fixed, also update "Panics" sections of
    // - [Mesh::compute_smooth_normals]
    // - [Mesh::with_computed_smooth_normals]
    // - [Mesh::compute_area_weighted_normals]
    // - [Mesh::with_computed_area_weighted_normals]
    pub fn compute_custom_smooth_normals(
        &mut self,
        mut per_triangle: impl FnMut([usize; 3], &[[f32; 3]], &mut [Vec3]),
    ) {
        assert!(
            matches!(self.primitive_topology, PrimitiveTopology::TriangleList),
            "smooth normals can only be computed on `TriangleList`s"
        );
        assert!(
            self.indices().is_some(),
            "smooth normals can only be computed on indexed meshes"
        );

        let positions = self
            .attribute(Mesh::ATTRIBUTE_POSITION)
            .unwrap()
            .as_float3()
            .expect("`Mesh::ATTRIBUTE_POSITION` vertex attributes should be of type `float3`");

        let mut normals = vec![Vec3::ZERO; positions.len()];

        self.indices()
            .unwrap()
            .iter()
            .collect::<Vec<usize>>()
            .chunks_exact(3)
            .for_each(|face| per_triangle([face[0], face[1], face[2]], positions, &mut normals));

        for normal in &mut normals {
            *normal = normal.try_normalize().unwrap_or(Vec3::ZERO);
        }

        self.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
    }

    /// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`].
    /// If the mesh is indexed, this defaults to smooth normals. Otherwise, it defaults to flat
    /// normals.
    ///
    /// (Alternatively, you can use [`Mesh::compute_normals`] to mutate an existing mesh in-place)
    ///
    /// # Panics
    /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`.
    /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`].
    #[must_use]
    pub fn with_computed_normals(mut self) -> Self {
        self.compute_normals();
        self
    }

    /// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`].
    ///
    /// (Alternatively, you can use [`Mesh::compute_flat_normals`] to mutate an existing mesh in-place)
    ///
    /// # Panics
    /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`.
    /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`].
    /// Panics if the mesh has indices defined
    #[must_use]
    pub fn with_computed_flat_normals(mut self) -> Self {
        self.compute_flat_normals();
        self
    }

    /// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`].
    ///
    /// (Alternatively, you can use [`Mesh::compute_smooth_normals`] to mutate an existing mesh in-place)
    ///
    /// This method weights normals by the angles of triangle corners connected to each vertex. If
    /// you would rather have the computed normals be weighted by triangle area, see
    /// [`Mesh::with_computed_area_weighted_normals`] instead.
    ///
    /// # Panics
    /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`.
    /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`].
    /// Panics if the mesh does not have indices defined.
    #[must_use]
    pub fn with_computed_smooth_normals(mut self) -> Self {
        self.compute_smooth_normals();
        self
    }

    /// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`].
    ///
    /// (Alternatively, you can use [`Mesh::compute_area_weighted_normals`] to mutate an existing mesh in-place)
    ///
    /// This method weights normals by the area of each triangle containing the vertex. Thus,
    /// larger triangles will skew the normals of their vertices towards their own normal more
    /// than smaller triangles will. If you would rather have the computed normals be influenced
    /// only by the angles of connected edges, see [`Mesh::with_computed_smooth_normals`] instead.
    ///
    /// # Panics
    /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`.
    /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`].
    /// Panics if the mesh does not have indices defined.
    #[must_use]
    pub fn with_computed_area_weighted_normals(mut self) -> Self {
        self.compute_area_weighted_normals();
        self
    }

    /// Generate tangents for the mesh using the `mikktspace` algorithm.
    ///
    /// Sets the [`Mesh::ATTRIBUTE_TANGENT`] attribute if successful.
    /// Requires a [`PrimitiveTopology::TriangleList`] topology and the [`Mesh::ATTRIBUTE_POSITION`], [`Mesh::ATTRIBUTE_NORMAL`] and [`Mesh::ATTRIBUTE_UV_0`] attributes set.
    #[cfg(feature = "bevy_mikktspace")]
    pub fn generate_tangents(&mut self) -> Result<(), super::GenerateTangentsError> {
        let tangents = super::generate_tangents_for_mesh(self)?;
        self.insert_attribute(Mesh::ATTRIBUTE_TANGENT, tangents);
        Ok(())
    }

    /// Consumes the mesh and returns a mesh with tangents generated using the `mikktspace` algorithm.
    ///
    /// The resulting mesh will have the [`Mesh::ATTRIBUTE_TANGENT`] attribute if successful.
    ///
    /// (Alternatively, you can use [`Mesh::generate_tangents`] to mutate an existing mesh in-place)
    ///
    /// Requires a [`PrimitiveTopology::TriangleList`] topology and the [`Mesh::ATTRIBUTE_POSITION`], [`Mesh::ATTRIBUTE_NORMAL`] and [`Mesh::ATTRIBUTE_UV_0`] attributes set.
    #[cfg(feature = "bevy_mikktspace")]
    pub fn with_generated_tangents(mut self) -> Result<Mesh, super::GenerateTangentsError> {
        self.generate_tangents()?;
        Ok(self)
    }

    /// Merges the [`Mesh`] data of `other` with `self`. The attributes and indices of `other` will be appended to `self`.
    ///
    /// Note that attributes of `other` that don't exist on `self` will be ignored.
    ///
    /// `Aabb` of entities with modified mesh are not updated automatically.
    ///
    /// # Errors
    ///
    /// If any of the following conditions are not met, this function errors:
    /// * All of the vertex attributes that have the same attribute id, must also
    ///   have the same attribute type.
    ///   For example two attributes with the same id, but where one is a
    ///   [`VertexAttributeValues::Float32`] and the other is a
    ///   [`VertexAttributeValues::Float32x3`], would be invalid.
    /// * Both meshes must have the same primitive topology.
    pub fn merge(&mut self, other: &Mesh) -> Result<(), MeshMergeError> {
        use VertexAttributeValues::*;

        // Check if the meshes `primitive_topology` field is the same,
        // as if that is not the case, the resulting mesh could (and most likely would)
        // be invalid.
        if self.primitive_topology != other.primitive_topology {
            return Err(MeshMergeError::IncompatiblePrimitiveTopology {
                self_primitive_topology: self.primitive_topology,
                other_primitive_topology: other.primitive_topology,
            });
        }

        // The indices of `other` should start after the last vertex of `self`.
        let index_offset = self.count_vertices();

        // Extend attributes of `self` with attributes of `other`.
        for (attribute, values) in self.attributes_mut() {
            if let Some(other_values) = other.attribute(attribute.id) {
                #[expect(
                    clippy::match_same_arms,
                    reason = "Although the bindings on some match arms may have different types, each variant has different semantics; thus it's not guaranteed that they will use the same type forever."
                )]
                match (values, other_values) {
                    (Float32(vec1), Float32(vec2)) => vec1.extend(vec2),
                    (Sint32(vec1), Sint32(vec2)) => vec1.extend(vec2),
                    (Uint32(vec1), Uint32(vec2)) => vec1.extend(vec2),
                    (Float32x2(vec1), Float32x2(vec2)) => vec1.extend(vec2),
                    (Sint32x2(vec1), Sint32x2(vec2)) => vec1.extend(vec2),
                    (Uint32x2(vec1), Uint32x2(vec2)) => vec1.extend(vec2),
                    (Float32x3(vec1), Float32x3(vec2)) => vec1.extend(vec2),
                    (Sint32x3(vec1), Sint32x3(vec2)) => vec1.extend(vec2),
                    (Uint32x3(vec1), Uint32x3(vec2)) => vec1.extend(vec2),
                    (Sint32x4(vec1), Sint32x4(vec2)) => vec1.extend(vec2),
                    (Uint32x4(vec1), Uint32x4(vec2)) => vec1.extend(vec2),
                    (Float32x4(vec1), Float32x4(vec2)) => vec1.extend(vec2),
                    (Sint16x2(vec1), Sint16x2(vec2)) => vec1.extend(vec2),
                    (Snorm16x2(vec1), Snorm16x2(vec2)) => vec1.extend(vec2),
                    (Uint16x2(vec1), Uint16x2(vec2)) => vec1.extend(vec2),
                    (Unorm16x2(vec1), Unorm16x2(vec2)) => vec1.extend(vec2),
                    (Sint16x4(vec1), Sint16x4(vec2)) => vec1.extend(vec2),
                    (Snorm16x4(vec1), Snorm16x4(vec2)) => vec1.extend(vec2),
                    (Uint16x4(vec1), Uint16x4(vec2)) => vec1.extend(vec2),
                    (Unorm16x4(vec1), Unorm16x4(vec2)) => vec1.extend(vec2),
                    (Sint8x2(vec1), Sint8x2(vec2)) => vec1.extend(vec2),
                    (Snorm8x2(vec1), Snorm8x2(vec2)) => vec1.extend(vec2),
                    (Uint8x2(vec1), Uint8x2(vec2)) => vec1.extend(vec2),
                    (Unorm8x2(vec1), Unorm8x2(vec2)) => vec1.extend(vec2),
                    (Sint8x4(vec1), Sint8x4(vec2)) => vec1.extend(vec2),
                    (Snorm8x4(vec1), Snorm8x4(vec2)) => vec1.extend(vec2),
                    (Uint8x4(vec1), Uint8x4(vec2)) => vec1.extend(vec2),
                    (Unorm8x4(vec1), Unorm8x4(vec2)) => vec1.extend(vec2),
                    _ => {
                        return Err(MeshMergeError::IncompatibleVertexAttributes {
                            self_attribute: *attribute,
                            other_attribute: other
                                .attribute_data(attribute.id)
                                .map(|data| data.attribute),
                        })
                    }
                }
            }
        }

        // Extend indices of `self` with indices of `other`.
        if let (Some(indices), Some(other_indices)) = (self.indices_mut(), other.indices()) {
            indices.extend(other_indices.iter().map(|i| (i + index_offset) as u32));
        }
        Ok(())
    }

    /// Transforms the vertex positions, normals, and tangents of the mesh by the given [`Transform`].
    ///
    /// `Aabb` of entities with modified mesh are not updated automatically.
    pub fn transformed_by(mut self, transform: Transform) -> Self {
        self.transform_by(transform);
        self
    }

    /// Transforms the vertex positions, normals, and tangents of the mesh in place by the given [`Transform`].
    ///
    /// `Aabb` of entities with modified mesh are not updated automatically.
    pub fn transform_by(&mut self, transform: Transform) {
        // Needed when transforming normals and tangents
        let scale_recip = 1. / transform.scale;
        debug_assert!(
            transform.scale.yzx() * transform.scale.zxy() != Vec3::ZERO,
            "mesh transform scale cannot be zero on more than one axis"
        );

        if let Some(VertexAttributeValues::Float32x3(positions)) =
            self.attribute_mut(Mesh::ATTRIBUTE_POSITION)
        {
            // Apply scale, rotation, and translation to vertex positions
            positions
                .iter_mut()
                .for_each(|pos| *pos = transform.transform_point(Vec3::from_slice(pos)).to_array());
        }

        // No need to transform normals or tangents if rotation is near identity and scale is uniform
        if transform.rotation.is_near_identity()
            && transform.scale.x == transform.scale.y
            && transform.scale.y == transform.scale.z
        {
            return;
        }

        if let Some(VertexAttributeValues::Float32x3(normals)) =
            self.attribute_mut(Mesh::ATTRIBUTE_NORMAL)
        {
            // Transform normals, taking into account non-uniform scaling and rotation
            normals.iter_mut().for_each(|normal| {
                *normal = (transform.rotation
                    * scale_normal(Vec3::from_array(*normal), scale_recip))
                .to_array();
            });
        }

        if let Some(VertexAttributeValues::Float32x4(tangents)) =
            self.attribute_mut(Mesh::ATTRIBUTE_TANGENT)
        {
            // Transform tangents, taking into account non-uniform scaling and rotation
            tangents.iter_mut().for_each(|tangent| {
                let handedness = tangent[3];
                let scaled_tangent = Vec3::from_slice(tangent) * transform.scale;
                *tangent = (transform.rotation * scaled_tangent.normalize_or_zero())
                    .extend(handedness)
                    .to_array();
            });
        }
    }

    /// Translates the vertex positions of the mesh by the given [`Vec3`].
    ///
    /// `Aabb` of entities with modified mesh are not updated automatically.
    pub fn translated_by(mut self, translation: Vec3) -> Self {
        self.translate_by(translation);
        self
    }

    /// Translates the vertex positions of the mesh in place by the given [`Vec3`].
    ///
    /// `Aabb` of entities with modified mesh are not updated automatically.
    pub fn translate_by(&mut self, translation: Vec3) {
        if translation == Vec3::ZERO {
            return;
        }

        if let Some(VertexAttributeValues::Float32x3(positions)) =
            self.attribute_mut(Mesh::ATTRIBUTE_POSITION)
        {
            // Apply translation to vertex positions
            positions
                .iter_mut()
                .for_each(|pos| *pos = (Vec3::from_slice(pos) + translation).to_array());
        }
    }

    /// Rotates the vertex positions, normals, and tangents of the mesh by the given [`Quat`].
    ///
    /// `Aabb` of entities with modified mesh are not updated automatically.
    pub fn rotated_by(mut self, rotation: Quat) -> Self {
        self.rotate_by(rotation);
        self
    }

    /// Rotates the vertex positions, normals, and tangents of the mesh in place by the given [`Quat`].
    ///
    /// `Aabb` of entities with modified mesh are not updated automatically.
    pub fn rotate_by(&mut self, rotation: Quat) {
        if let Some(VertexAttributeValues::Float32x3(positions)) =
            self.attribute_mut(Mesh::ATTRIBUTE_POSITION)
        {
            // Apply rotation to vertex positions
            positions
                .iter_mut()
                .for_each(|pos| *pos = (rotation * Vec3::from_slice(pos)).to_array());
        }

        // No need to transform normals or tangents if rotation is near identity
        if rotation.is_near_identity() {
            return;
        }

        if let Some(VertexAttributeValues::Float32x3(normals)) =
            self.attribute_mut(Mesh::ATTRIBUTE_NORMAL)
        {
            // Transform normals
            normals.iter_mut().for_each(|normal| {
                *normal = (rotation * Vec3::from_slice(normal).normalize_or_zero()).to_array();
            });
        }

        if let Some(VertexAttributeValues::Float32x4(tangents)) =
            self.attribute_mut(Mesh::ATTRIBUTE_TANGENT)
        {
            // Transform tangents
            tangents.iter_mut().for_each(|tangent| {
                let handedness = tangent[3];
                *tangent = (rotation * Vec3::from_slice(tangent).normalize_or_zero())
                    .extend(handedness)
                    .to_array();
            });
        }
    }

    /// Scales the vertex positions, normals, and tangents of the mesh by the given [`Vec3`].
    ///
    /// `Aabb` of entities with modified mesh are not updated automatically.
    pub fn scaled_by(mut self, scale: Vec3) -> Self {
        self.scale_by(scale);
        self
    }

    /// Scales the vertex positions, normals, and tangents of the mesh in place by the given [`Vec3`].
    ///
    /// `Aabb` of entities with modified mesh are not updated automatically.
    pub fn scale_by(&mut self, scale: Vec3) {
        // Needed when transforming normals and tangents
        let scale_recip = 1. / scale;
        debug_assert!(
            scale.yzx() * scale.zxy() != Vec3::ZERO,
            "mesh transform scale cannot be zero on more than one axis"
        );

        if let Some(VertexAttributeValues::Float32x3(positions)) =
            self.attribute_mut(Mesh::ATTRIBUTE_POSITION)
        {
            // Apply scale to vertex positions
            positions
                .iter_mut()
                .for_each(|pos| *pos = (scale * Vec3::from_slice(pos)).to_array());
        }

        // No need to transform normals or tangents if scale is uniform
        if scale.x == scale.y && scale.y == scale.z {
            return;
        }

        if let Some(VertexAttributeValues::Float32x3(normals)) =
            self.attribute_mut(Mesh::ATTRIBUTE_NORMAL)
        {
            // Transform normals, taking into account non-uniform scaling
            normals.iter_mut().for_each(|normal| {
                *normal = scale_normal(Vec3::from_array(*normal), scale_recip).to_array();
            });
        }

        if let Some(VertexAttributeValues::Float32x4(tangents)) =
            self.attribute_mut(Mesh::ATTRIBUTE_TANGENT)
        {
            // Transform tangents, taking into account non-uniform scaling
            tangents.iter_mut().for_each(|tangent| {
                let handedness = tangent[3];
                let scaled_tangent = Vec3::from_slice(tangent) * scale;
                *tangent = scaled_tangent
                    .normalize_or_zero()
                    .extend(handedness)
                    .to_array();
            });
        }
    }

    /// Normalize joint weights so they sum to 1.
    pub fn normalize_joint_weights(&mut self) {
        if let Some(joints) = self.attribute_mut(Self::ATTRIBUTE_JOINT_WEIGHT) {
            let VertexAttributeValues::Float32x4(joints) = joints else {
                panic!("unexpected joint weight format");
            };

            for weights in joints.iter_mut() {
                // force negative weights to zero
                weights.iter_mut().for_each(|w| *w = w.max(0.0));

                let sum: f32 = weights.iter().sum();
                if sum == 0.0 {
                    // all-zero weights are invalid
                    weights[0] = 1.0;
                } else {
                    let recip = sum.recip();
                    for weight in weights.iter_mut() {
                        *weight *= recip;
                    }
                }
            }
        }
    }

    /// Get a list of this Mesh's [triangles] as an iterator if possible.
    ///
    /// Returns an error if any of the following conditions are met (see [`MeshTrianglesError`]):
    /// * The Mesh's [primitive topology] is not `TriangleList` or `TriangleStrip`.
    /// * The Mesh is missing position or index data.
    /// * The Mesh's position data has the wrong format (not `Float32x3`).
    ///
    /// [primitive topology]: PrimitiveTopology
    /// [triangles]: Triangle3d
    pub fn triangles(&self) -> Result<impl Iterator<Item = Triangle3d> + '_, MeshTrianglesError> {
        let Some(position_data) = self.attribute(Mesh::ATTRIBUTE_POSITION) else {
            return Err(MeshTrianglesError::MissingPositions);
        };

        let Some(vertices) = position_data.as_float3() else {
            return Err(MeshTrianglesError::PositionsFormat);
        };

        let Some(indices) = self.indices() else {
            return Err(MeshTrianglesError::MissingIndices);
        };

        match self.primitive_topology {
            PrimitiveTopology::TriangleList => {
                // When indices reference out-of-bounds vertex data, the triangle is omitted.
                // This implicitly truncates the indices to a multiple of 3.
                let iterator = match indices {
                    Indices::U16(vec) => FourIterators::First(
                        vec.as_slice()
                            .chunks_exact(3)
                            .flat_map(move |indices| indices_to_triangle(vertices, indices)),
                    ),
                    Indices::U32(vec) => FourIterators::Second(
                        vec.as_slice()
                            .chunks_exact(3)
                            .flat_map(move |indices| indices_to_triangle(vertices, indices)),
                    ),
                };

                return Ok(iterator);
            }

            PrimitiveTopology::TriangleStrip => {
                // When indices reference out-of-bounds vertex data, the triangle is omitted.
                // If there aren't enough indices to make a triangle, then an empty vector will be
                // returned.
                let iterator = match indices {
                    Indices::U16(vec) => {
                        FourIterators::Third(vec.as_slice().windows(3).enumerate().flat_map(
                            move |(i, indices)| {
                                if i % 2 == 0 {
                                    indices_to_triangle(vertices, indices)
                                } else {
                                    indices_to_triangle(
                                        vertices,
                                        &[indices[1], indices[0], indices[2]],
                                    )
                                }
                            },
                        ))
                    }
                    Indices::U32(vec) => {
                        FourIterators::Fourth(vec.as_slice().windows(3).enumerate().flat_map(
                            move |(i, indices)| {
                                if i % 2 == 0 {
                                    indices_to_triangle(vertices, indices)
                                } else {
                                    indices_to_triangle(
                                        vertices,
                                        &[indices[1], indices[0], indices[2]],
                                    )
                                }
                            },
                        ))
                    }
                };

                return Ok(iterator);
            }

            _ => {
                return Err(MeshTrianglesError::WrongTopology);
            }
        };

        fn indices_to_triangle<T: TryInto<usize> + Copy>(
            vertices: &[[f32; 3]],
            indices: &[T],
        ) -> Option<Triangle3d> {
            let vert0: Vec3 = Vec3::from(*vertices.get(indices[0].try_into().ok()?)?);
            let vert1: Vec3 = Vec3::from(*vertices.get(indices[1].try_into().ok()?)?);
            let vert2: Vec3 = Vec3::from(*vertices.get(indices[2].try_into().ok()?)?);
            Some(Triangle3d {
                vertices: [vert0, vert1, vert2],
            })
        }
    }
}

#[cfg(feature = "morph")]
impl Mesh {
    /// Whether this mesh has morph targets.
    pub fn has_morph_targets(&self) -> bool {
        self.morph_targets.is_some()
    }

    /// Set [morph targets] image for this mesh. This requires a "morph target image". See [`MorphTargetImage`](crate::morph::MorphTargetImage) for info.
    ///
    /// [morph targets]: https://en.wikipedia.org/wiki/Morph_target_animation
    pub fn set_morph_targets(&mut self, morph_targets: Handle<Image>) {
        self.morph_targets = Some(morph_targets);
    }

    pub fn morph_targets(&self) -> Option<&Handle<Image>> {
        self.morph_targets.as_ref()
    }

    /// Consumes the mesh and returns a mesh with the given [morph targets].
    ///
    /// This requires a "morph target image". See [`MorphTargetImage`](crate::morph::MorphTargetImage) for info.
    ///
    /// (Alternatively, you can use [`Mesh::set_morph_targets`] to mutate an existing mesh in-place)
    ///
    /// [morph targets]: https://en.wikipedia.org/wiki/Morph_target_animation
    #[must_use]
    pub fn with_morph_targets(mut self, morph_targets: Handle<Image>) -> Self {
        self.set_morph_targets(morph_targets);
        self
    }

    /// Sets the names of each morph target. This should correspond to the order of the morph targets in `set_morph_targets`.
    pub fn set_morph_target_names(&mut self, names: Vec<String>) {
        self.morph_target_names = Some(names);
    }

    /// Consumes the mesh and returns a mesh with morph target names.
    /// Names should correspond to the order of the morph targets in `set_morph_targets`.
    ///
    /// (Alternatively, you can use [`Mesh::set_morph_target_names`] to mutate an existing mesh in-place)
    #[must_use]
    pub fn with_morph_target_names(mut self, names: Vec<String>) -> Self {
        self.set_morph_target_names(names);
        self
    }

    /// Gets a list of all morph target names, if they exist.
    pub fn morph_target_names(&self) -> Option<&[String]> {
        self.morph_target_names.as_deref()
    }
}

/// Correctly scales and renormalizes an already normalized `normal` by the scale determined by its reciprocal `scale_recip`
pub(crate) fn scale_normal(normal: Vec3, scale_recip: Vec3) -> Vec3 {
    // This is basically just `normal * scale_recip` but with the added rule that `0. * anything == 0.`
    // This is necessary because components of `scale_recip` may be infinities, which do not multiply to zero
    let n = Vec3::select(normal.cmpeq(Vec3::ZERO), Vec3::ZERO, normal * scale_recip);

    // If n is finite, no component of `scale_recip` was infinite or the normal was perpendicular to the scale
    // else the scale had at least one zero-component and the normal needs to point along the direction of that component
    if n.is_finite() {
        n.normalize_or_zero()
    } else {
        Vec3::select(n.abs().cmpeq(Vec3::INFINITY), n.signum(), Vec3::ZERO).normalize()
    }
}

impl core::ops::Mul<Mesh> for Transform {
    type Output = Mesh;

    fn mul(self, rhs: Mesh) -> Self::Output {
        rhs.transformed_by(self)
    }
}

/// A version of [`Mesh`] suitable for serializing for short-term transfer.
///
/// [`Mesh`] does not implement [`Serialize`] / [`Deserialize`] because it is made with the renderer in mind.
/// It is not a general-purpose mesh implementation, and its internals are subject to frequent change.
/// As such, storing a [`Mesh`] on disk is highly discouraged.
///
/// But there are still some valid use cases for serializing a [`Mesh`], namely transferring meshes between processes.
/// To support this, you can create a [`SerializedMesh`] from a [`Mesh`] with [`SerializedMesh::from_mesh`],
/// and then deserialize it with [`SerializedMesh::deserialize`]. The caveats are:
/// - The mesh representation is not valid across different versions of Bevy.
/// - This conversion is lossy. Only the following information is preserved:
///   - Primitive topology
///   - Vertex attributes
///   - Indices
/// - Custom attributes that were not specified with [`MeshDeserializer::add_custom_vertex_attribute`] will be ignored while deserializing.
#[cfg(feature = "serialize")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializedMesh {
    primitive_topology: PrimitiveTopology,
    attributes: Vec<(MeshVertexAttributeId, SerializedMeshAttributeData)>,
    indices: Option<Indices>,
}

#[cfg(feature = "serialize")]
impl SerializedMesh {
    /// Create a [`SerializedMesh`] from a [`Mesh`]. See the documentation for [`SerializedMesh`] for caveats.
    pub fn from_mesh(mesh: Mesh) -> Self {
        Self {
            primitive_topology: mesh.primitive_topology,
            attributes: mesh
                .attributes
                .into_iter()
                .map(|(id, data)| {
                    (
                        id,
                        SerializedMeshAttributeData::from_mesh_attribute_data(data),
                    )
                })
                .collect(),
            indices: mesh.indices,
        }
    }

    /// Create a [`Mesh`] from a [`SerializedMesh`]. See the documentation for [`SerializedMesh`] for caveats.
    ///
    /// Use [`MeshDeserializer`] if you need to pass extra options to the deserialization process, such as specifying custom vertex attributes.
    pub fn into_mesh(self) -> Mesh {
        MeshDeserializer::default().deserialize(self)
    }
}

/// Use to specify extra options when deserializing a [`SerializedMesh`] into a [`Mesh`].
#[cfg(feature = "serialize")]
pub struct MeshDeserializer {
    custom_vertex_attributes: HashMap<Box<str>, MeshVertexAttribute>,
}

#[cfg(feature = "serialize")]
impl Default for MeshDeserializer {
    fn default() -> Self {
        // Written like this so that the compiler can validate that we use all the built-in attributes.
        // If you just added a new attribute and got a compile error, please add it to this list :)
        const BUILTINS: [MeshVertexAttribute; Mesh::FIRST_AVAILABLE_CUSTOM_ATTRIBUTE as usize] = [
            Mesh::ATTRIBUTE_POSITION,
            Mesh::ATTRIBUTE_NORMAL,
            Mesh::ATTRIBUTE_UV_0,
            Mesh::ATTRIBUTE_UV_1,
            Mesh::ATTRIBUTE_TANGENT,
            Mesh::ATTRIBUTE_COLOR,
            Mesh::ATTRIBUTE_JOINT_WEIGHT,
            Mesh::ATTRIBUTE_JOINT_INDEX,
        ];
        Self {
            custom_vertex_attributes: BUILTINS
                .into_iter()
                .map(|attribute| (attribute.name.into(), attribute))
                .collect(),
        }
    }
}

#[cfg(feature = "serialize")]
impl MeshDeserializer {
    /// Create a new [`MeshDeserializer`].
    pub fn new() -> Self {
        Self::default()
    }

    /// Register a custom vertex attribute to the deserializer. Custom vertex attributes that were not added with this method will be ignored while deserializing.
    pub fn add_custom_vertex_attribute(
        &mut self,
        name: &str,
        attribute: MeshVertexAttribute,
    ) -> &mut Self {
        self.custom_vertex_attributes.insert(name.into(), attribute);
        self
    }

    /// Deserialize a [`SerializedMesh`] into a [`Mesh`].
    ///
    /// See the documentation for [`SerializedMesh`] for caveats.
    pub fn deserialize(&self, serialized_mesh: SerializedMesh) -> Mesh {
        Mesh {
            attributes:
                serialized_mesh
                .attributes
                .into_iter()
                .filter_map(|(id, data)| {
                    let attribute = data.attribute.clone();
                    let Some(data) =
                        data.try_into_mesh_attribute_data(&self.custom_vertex_attributes)
                    else {
                        warn!(
                            "Deserialized mesh contains custom vertex attribute {attribute:?} that \
                            was not specified with `MeshDeserializer::add_custom_vertex_attribute`. Ignoring."
                        );
                        return None;
                    };
                    Some((id, data))
                })
                .collect(),
            indices: serialized_mesh.indices,
            ..Mesh::new(serialized_mesh.primitive_topology, RenderAssetUsages::default())
        }
    }
}

/// Error that can occur when calling [`Mesh::merge`].
#[derive(Error, Debug, Clone)]
pub enum MeshMergeError {
    #[error("Incompatible vertex attribute types: {} and {}", self_attribute.name, other_attribute.map(|a| a.name).unwrap_or("None"))]
    IncompatibleVertexAttributes {
        self_attribute: MeshVertexAttribute,
        other_attribute: Option<MeshVertexAttribute>,
    },
    #[error(
        "Incompatible primitive topologies: {:?} and {:?}",
        self_primitive_topology,
        other_primitive_topology
    )]
    IncompatiblePrimitiveTopology {
        self_primitive_topology: PrimitiveTopology,
        other_primitive_topology: PrimitiveTopology,
    },
}

#[cfg(test)]
mod tests {
    use super::Mesh;
    #[cfg(feature = "serialize")]
    use super::SerializedMesh;
    use crate::mesh::{Indices, MeshWindingInvertError, VertexAttributeValues};
    use crate::PrimitiveTopology;
    use bevy_asset::RenderAssetUsages;
    use bevy_math::primitives::Triangle3d;
    use bevy_math::Vec3;
    use bevy_transform::components::Transform;

    #[test]
    #[should_panic]
    fn panic_invalid_format() {
        let _mesh = Mesh::new(
            PrimitiveTopology::TriangleList,
            RenderAssetUsages::default(),
        )
        .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, vec![[0.0, 0.0, 0.0]]);
    }

    #[test]
    fn transform_mesh() {
        let mesh = Mesh::new(
            PrimitiveTopology::TriangleList,
            RenderAssetUsages::default(),
        )
        .with_inserted_attribute(
            Mesh::ATTRIBUTE_POSITION,
            vec![[-1., -1., 2.], [1., -1., 2.], [0., 1., 2.]],
        )
        .with_inserted_attribute(
            Mesh::ATTRIBUTE_NORMAL,
            vec![
                Vec3::new(-1., -1., 1.).normalize().to_array(),
                Vec3::new(1., -1., 1.).normalize().to_array(),
                [0., 0., 1.],
            ],
        )
        .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, vec![[0., 0.], [1., 0.], [0.5, 1.]]);

        let mesh = mesh.transformed_by(
            Transform::from_translation(Vec3::splat(-2.)).with_scale(Vec3::new(2., 0., -1.)),
        );

        if let Some(VertexAttributeValues::Float32x3(positions)) =
            mesh.attribute(Mesh::ATTRIBUTE_POSITION)
        {
            // All positions are first scaled resulting in `vec![[-2, 0., -2.], [2., 0., -2.], [0., 0., -2.]]`
            // and then shifted by `-2.` along each axis
            assert_eq!(
                positions,
                &vec![[-4.0, -2.0, -4.0], [0.0, -2.0, -4.0], [-2.0, -2.0, -4.0]]
            );
        } else {
            panic!("Mesh does not have a position attribute");
        }

        if let Some(VertexAttributeValues::Float32x3(normals)) =
            mesh.attribute(Mesh::ATTRIBUTE_NORMAL)
        {
            assert_eq!(normals, &vec![[0., -1., 0.], [0., -1., 0.], [0., 0., -1.]]);
        } else {
            panic!("Mesh does not have a normal attribute");
        }

        if let Some(VertexAttributeValues::Float32x2(uvs)) = mesh.attribute(Mesh::ATTRIBUTE_UV_0) {
            assert_eq!(uvs, &vec![[0., 0.], [1., 0.], [0.5, 1.]]);
        } else {
            panic!("Mesh does not have a uv attribute");
        }
    }

    #[test]
    fn point_list_mesh_invert_winding() {
        let mesh = Mesh::new(PrimitiveTopology::PointList, RenderAssetUsages::default())
            .with_inserted_indices(Indices::U32(vec![]));
        assert!(matches!(
            mesh.with_inverted_winding(),
            Err(MeshWindingInvertError::WrongTopology)
        ));
    }

    #[test]
    fn line_list_mesh_invert_winding() {
        let mesh = Mesh::new(PrimitiveTopology::LineList, RenderAssetUsages::default())
            .with_inserted_indices(Indices::U32(vec![0, 1, 1, 2, 2, 3]));
        let mesh = mesh.with_inverted_winding().unwrap();
        assert_eq!(
            mesh.indices().unwrap().iter().collect::<Vec<usize>>(),
            vec![3, 2, 2, 1, 1, 0]
        );
    }

    #[test]
    fn line_list_mesh_invert_winding_fail() {
        let mesh = Mesh::new(PrimitiveTopology::LineList, RenderAssetUsages::default())
            .with_inserted_indices(Indices::U32(vec![0, 1, 1]));
        assert!(matches!(
            mesh.with_inverted_winding(),
            Err(MeshWindingInvertError::AbruptIndicesEnd)
        ));
    }

    #[test]
    fn line_strip_mesh_invert_winding() {
        let mesh = Mesh::new(PrimitiveTopology::LineStrip, RenderAssetUsages::default())
            .with_inserted_indices(Indices::U32(vec![0, 1, 2, 3]));
        let mesh = mesh.with_inverted_winding().unwrap();
        assert_eq!(
            mesh.indices().unwrap().iter().collect::<Vec<usize>>(),
            vec![3, 2, 1, 0]
        );
    }

    #[test]
    fn triangle_list_mesh_invert_winding() {
        let mesh = Mesh::new(
            PrimitiveTopology::TriangleList,
            RenderAssetUsages::default(),
        )
        .with_inserted_indices(Indices::U32(vec![
            0, 3, 1, // First triangle
            1, 3, 2, // Second triangle
        ]));
        let mesh = mesh.with_inverted_winding().unwrap();
        assert_eq!(
            mesh.indices().unwrap().iter().collect::<Vec<usize>>(),
            vec![
                0, 1, 3, // First triangle
                1, 2, 3, // Second triangle
            ]
        );
    }

    #[test]
    fn triangle_list_mesh_invert_winding_fail() {
        let mesh = Mesh::new(
            PrimitiveTopology::TriangleList,
            RenderAssetUsages::default(),
        )
        .with_inserted_indices(Indices::U32(vec![0, 3, 1, 2]));
        assert!(matches!(
            mesh.with_inverted_winding(),
            Err(MeshWindingInvertError::AbruptIndicesEnd)
        ));
    }

    #[test]
    fn triangle_strip_mesh_invert_winding() {
        let mesh = Mesh::new(
            PrimitiveTopology::TriangleStrip,
            RenderAssetUsages::default(),
        )
        .with_inserted_indices(Indices::U32(vec![0, 1, 2, 3]));
        let mesh = mesh.with_inverted_winding().unwrap();
        assert_eq!(
            mesh.indices().unwrap().iter().collect::<Vec<usize>>(),
            vec![3, 2, 1, 0]
        );
    }

    #[test]
    fn compute_area_weighted_normals() {
        let mut mesh = Mesh::new(
            PrimitiveTopology::TriangleList,
            RenderAssetUsages::default(),
        );

        //  z      y
        //  |    /
        //  3---2
        //  | /  \
        //  0-----1--x

        mesh.insert_attribute(
            Mesh::ATTRIBUTE_POSITION,
            vec![[0., 0., 0.], [1., 0., 0.], [0., 1., 0.], [0., 0., 1.]],
        );
        mesh.insert_indices(Indices::U16(vec![0, 1, 2, 0, 2, 3]));
        mesh.compute_area_weighted_normals();
        let normals = mesh
            .attribute(Mesh::ATTRIBUTE_NORMAL)
            .unwrap()
            .as_float3()
            .unwrap();
        assert_eq!(4, normals.len());
        // 0
        assert_eq!(Vec3::new(1., 0., 1.).normalize().to_array(), normals[0]);
        // 1
        assert_eq!([0., 0., 1.], normals[1]);
        // 2
        assert_eq!(Vec3::new(1., 0., 1.).normalize().to_array(), normals[2]);
        // 3
        assert_eq!([1., 0., 0.], normals[3]);
    }

    #[test]
    fn compute_area_weighted_normals_proportionate() {
        let mut mesh = Mesh::new(
            PrimitiveTopology::TriangleList,
            RenderAssetUsages::default(),
        );

        //  z      y
        //  |    /
        //  3---2..
        //  | /    \
        //  0-------1---x

        mesh.insert_attribute(
            Mesh::ATTRIBUTE_POSITION,
            vec![[0., 0., 0.], [2., 0., 0.], [0., 1., 0.], [0., 0., 1.]],
        );
        mesh.insert_indices(Indices::U16(vec![0, 1, 2, 0, 2, 3]));
        mesh.compute_area_weighted_normals();
        let normals = mesh
            .attribute(Mesh::ATTRIBUTE_NORMAL)
            .unwrap()
            .as_float3()
            .unwrap();
        assert_eq!(4, normals.len());
        // 0
        assert_eq!(Vec3::new(1., 0., 2.).normalize().to_array(), normals[0]);
        // 1
        assert_eq!([0., 0., 1.], normals[1]);
        // 2
        assert_eq!(Vec3::new(1., 0., 2.).normalize().to_array(), normals[2]);
        // 3
        assert_eq!([1., 0., 0.], normals[3]);
    }

    #[test]
    fn compute_angle_weighted_normals() {
        // CuboidMeshBuilder duplicates vertices (even though it is indexed)

        //   5---------4
        //  /|        /|
        // 1-+-------0 |
        // | 6-------|-7
        // |/        |/
        // 2---------3
        let verts = vec![
            [1.0, 1.0, 1.0],
            [-1.0, 1.0, 1.0],
            [-1.0, -1.0, 1.0],
            [1.0, -1.0, 1.0],
            [1.0, 1.0, -1.0],
            [-1.0, 1.0, -1.0],
            [-1.0, -1.0, -1.0],
            [1.0, -1.0, -1.0],
        ];

        let indices = Indices::U16(vec![
            0, 1, 2, 2, 3, 0, // front
            5, 4, 7, 7, 6, 5, // back
            1, 5, 6, 6, 2, 1, // left
            4, 0, 3, 3, 7, 4, // right
            4, 5, 1, 1, 0, 4, // top
            3, 2, 6, 6, 7, 3, // bottom
        ]);
        let mut mesh = Mesh::new(
            PrimitiveTopology::TriangleList,
            RenderAssetUsages::default(),
        );
        mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, verts);
        mesh.insert_indices(indices);
        mesh.compute_smooth_normals();

        let normals = mesh
            .attribute(Mesh::ATTRIBUTE_NORMAL)
            .unwrap()
            .as_float3()
            .unwrap();

        for new in normals.iter().copied().flatten() {
            // std impl is unstable
            const FRAC_1_SQRT_3: f32 = 0.57735026;
            const MIN: f32 = FRAC_1_SQRT_3 - f32::EPSILON;
            const MAX: f32 = FRAC_1_SQRT_3 + f32::EPSILON;
            assert!(new.abs() >= MIN, "{new} < {MIN}");
            assert!(new.abs() <= MAX, "{new} > {MAX}");
        }
    }

    #[test]
    fn triangles_from_triangle_list() {
        let mut mesh = Mesh::new(
            PrimitiveTopology::TriangleList,
            RenderAssetUsages::default(),
        );
        mesh.insert_attribute(
            Mesh::ATTRIBUTE_POSITION,
            vec![[0., 0., 0.], [1., 0., 0.], [1., 1., 0.], [0., 1., 0.]],
        );
        mesh.insert_indices(Indices::U32(vec![0, 1, 2, 2, 3, 0]));
        assert_eq!(
            vec![
                Triangle3d {
                    vertices: [
                        Vec3::new(0., 0., 0.),
                        Vec3::new(1., 0., 0.),
                        Vec3::new(1., 1., 0.),
                    ]
                },
                Triangle3d {
                    vertices: [
                        Vec3::new(1., 1., 0.),
                        Vec3::new(0., 1., 0.),
                        Vec3::new(0., 0., 0.),
                    ]
                }
            ],
            mesh.triangles().unwrap().collect::<Vec<Triangle3d>>()
        );
    }

    #[test]
    fn triangles_from_triangle_strip() {
        let mut mesh = Mesh::new(
            PrimitiveTopology::TriangleStrip,
            RenderAssetUsages::default(),
        );
        // Triangles: (0, 1, 2), (2, 1, 3), (2, 3, 4), (4, 3, 5)
        //
        // 4 - 5
        // | \ |
        // 2 - 3
        // | \ |
        // 0 - 1
        let positions: Vec<Vec3> = [
            [0., 0., 0.],
            [1., 0., 0.],
            [0., 1., 0.],
            [1., 1., 0.],
            [0., 2., 0.],
            [1., 2., 0.],
        ]
        .into_iter()
        .map(Vec3::from_array)
        .collect();
        mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions.clone());
        mesh.insert_indices(Indices::U32(vec![0, 1, 2, 3, 4, 5]));
        assert_eq!(
            vec![
                Triangle3d {
                    vertices: [positions[0], positions[1], positions[2]]
                },
                Triangle3d {
                    vertices: [positions[2], positions[1], positions[3]]
                },
                Triangle3d {
                    vertices: [positions[2], positions[3], positions[4]]
                },
                Triangle3d {
                    vertices: [positions[4], positions[3], positions[5]]
                },
            ],
            mesh.triangles().unwrap().collect::<Vec<Triangle3d>>()
        );
    }

    #[cfg(feature = "serialize")]
    #[test]
    fn serialize_deserialize_mesh() {
        let mut mesh = Mesh::new(
            PrimitiveTopology::TriangleList,
            RenderAssetUsages::default(),
        );

        mesh.insert_attribute(
            Mesh::ATTRIBUTE_POSITION,
            vec![[0., 0., 0.], [2., 0., 0.], [0., 1., 0.], [0., 0., 1.]],
        );
        mesh.insert_indices(Indices::U16(vec![0, 1, 2, 0, 2, 3]));

        let serialized_mesh = SerializedMesh::from_mesh(mesh.clone());
        let serialized_string = serde_json::to_string(&serialized_mesh).unwrap();
        let serialized_mesh_from_string: SerializedMesh =
            serde_json::from_str(&serialized_string).unwrap();
        let deserialized_mesh = serialized_mesh_from_string.into_mesh();
        assert_eq!(mesh, deserialized_mesh);
    }
}
